package br.ufpb.di.ppgi.util.vraptor;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.List;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import net.vidageek.mirror.dsl.Mirror;

import org.apache.log4j.Logger;

import br.com.caelum.vraptor.ioc.Component;
import br.com.caelum.vraptor.ioc.Container;
import br.com.caelum.vraptor.ioc.RequestScoped;

/**
 * @author Tomaz Lavieri
 */
@Component
@RequestScoped
public class ContainerRequestScoped implements Serializable{
	private static final long serialVersionUID = -7663189047613651842L;
	private static final Logger LOGGER = Logger.getLogger(ContainerRequestScoped.class);
	private static final String MSG_STATE = "The current thread is not associate to a RequestScoped, cant get the target [%s].";
	private static final ThreadLocal<Container> CONTAINERS = new ThreadLocal<Container>();
	
	private final Container container;
	public ContainerRequestScoped(Container container) {
		this.container = container;
	}
	
	@PostConstruct
	public void postConstruct() {
		CONTAINERS.set(container);
	}
	
	@PreDestroy
	public void preDestruct() {
		CONTAINERS.set(null);
	}
	
	
	/**
	 * Resgata a instancia do <tt>target</tt> associada a requisição de quem invoca.<br/>
	 * <br/>
	 * Para previnir contra {@link IllegalStateException} veja {@link #isRequestThread()}.
	 * @param <T> o tipo de Objeto que se quer resgatar.
	 * @param target a classe alvo para o resgate.
	 * @return a instancia associada ao escopo.
	 * @throws IllegalStateException Caso a <tt>Thread</tt> de quem invocar não esteja
	 * 			associada a nenhuma Requisição.
	 * @see #isRequestThread()
	 */
	public static <T> T instanceFor(Class<T> target) throws IllegalStateException {
		if (!isRequestThread()) {
			String msg = String.format(
					MSG_STATE, 
					target.getName());
			IllegalStateException ex = new IllegalStateException(msg);
			LOGGER.error(msg, ex);
			throw ex;
		}
		return CONTAINERS.get().instanceFor(target);
	}
	
	/**
	 * Informa se a sua Thread esta associada a uma requisição.
	 */
	public static boolean isRequestThread() {
		return CONTAINERS.get() != null;
	}
	
	/**
	 * Injeta os componentes necessarios em todos os campos do 
	 * <tt>target</tt> que estiverem anotados com 
	 * <tt>@{@link Inject}</tt>
	 * @param target o alvo onde devera ser injetado os componentes.
	 */
	public static void injectOn(Object target) {
		List<Field> fields = new Mirror().on(target.getClass()).reflectAll().fields();
		for (Field field : fields) {
			if (field.isAnnotationPresent(Inject.class)) {
				Object component = instanceFor(field.getType());
				new Mirror().on(target).set().field(field).withValue(component);
			}
		}
	}
}
